package com.iwhalecloud.bss.kite.cucc.web.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.iwhalecloud.bss.kite.common.constant.SymbolConsts;
import com.iwhalecloud.bss.kite.common.util.KiteListUtils;
import com.iwhalecloud.bss.kite.common.util.KiteMapUtils;
import com.iwhalecloud.bss.kite.common.util.KiteStringUtils;
import com.iwhalecloud.bss.kite.cucc.client.api.reception.ICuccLoginService;
import com.iwhalecloud.bss.kite.cucc.client.api.reception.ICustReceptionService;
import com.iwhalecloud.bss.kite.cucc.client.dto.login.CuccLoginInfoDTO;
import com.iwhalecloud.bss.kite.cucc.client.dto.login.CuccOrgInfoDTO;
import com.iwhalecloud.bss.kite.cucc.client.dto.login.auth.CuccAuthLoginInfoDTO;
import com.iwhalecloud.bss.kite.cucc.client.dto.login.auth.CuccAuthOrgInfoDTO;
import com.iwhalecloud.bss.kite.cucc.common.constant.LocalDcPublicConsts;
import com.iwhalecloud.bss.kite.cucc.common.constant.LocalInfConsts;
import com.iwhalecloud.bss.kite.cucc.dataservice.cache.CuccSessionIdCache;
import com.iwhalecloud.bss.kite.dataservice.cache.DcPublicCache;
import com.iwhalecloud.bss.kite.dataservice.entity.DcPublic;
import com.ztesoft.bss.base.annotation.IgnoreSession;
import com.ztesoft.bss.common.bo.LoginInfo;
import com.ztesoft.bss.common.bo.UserInfo;
import com.ztesoft.bss.common.exception.BssException;
import com.ztesoft.bss.common.util.ContextUtil;
import com.ztesoft.bss.common.util.SpringUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

public abstract class AbstractSessionInterceptor extends HandlerInterceptorAdapter {
    protected static final String NAME_SESSION = "SESSION";
    protected static final String NAME_USER_ID = "user_code";

    protected static final String NAME_ORG_ID = "org_id";

    protected static final String NAME_EPARCHY_CODE = "eparchy_code";

    protected static final String NAME_PROVINCE_CODE = "province_code";

    protected static final String NAME_KITE_TOKEN = "kite_token";

    protected static final int GRAY_INDEX = 2;

    protected static final List<String> GRAY_FLAGS = Lists.newArrayList("g", "r", "a", "y");

    protected static final List<String> NON_GRAY_FLAGS = Lists.newArrayList("b", "c", "d", "e", "f", "h", "i", "j", "k",
        "l", "m", "n", "o", "p", "q", "s", "t", "u", "v", "w", "x", "z");

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
        throws IOException, ServletException {
        if (request.getRequestURI().contains("/swagger") || request.getRequestURI().contains("/local/cache")
            || request.getRequestURI().contains("/static/data") || request.getRequestURI().contains("/cache/refresh")) {
            return true;
        }
        LocaleContextHolder.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        if (HandlerMethod.class.isAssignableFrom(handler.getClass())) {
            // 有标注IgnoreSession的请求，不校验session
            HandlerMethod method = (HandlerMethod) handler;
            IgnoreSession is = method.getMethodAnnotation(IgnoreSession.class);
            if (is != null) {
                return true;
            }
        }

        return handle(request, response, handler);
    }

    protected CuccLoginInfoDTO getLoginInfo(String sessionId) {
        ICustReceptionService custReceptionService = SpringUtil.getBean(ICustReceptionService.class);
        return custReceptionService.getLoginInfo(sessionId).getResultObject();
    }



    /**
     * 菜单跳转前先设置灰度标识
     * @param request
     * @param response
     * @param sessionId
     */
    protected void handleMenu(HttpServletRequest request, HttpServletResponse response, String sessionId) {
        if (StringUtils.isBlank(sessionId)) {
            return;
        }

        ICustReceptionService custReceptionService = SpringUtil.getBean(ICustReceptionService.class);
        CuccAuthLoginInfoDTO cuccAuthLoginInfo = custReceptionService.pretendLogin(sessionId);
        if (Objects.isNull(cuccAuthLoginInfo)) {
            return;
        }

        ICuccLoginService loginService = SpringUtil.getBean(ICuccLoginService.class);

        loginService.saveLoginInfoFromPortal(sessionId, cuccAuthLoginInfo);
        // 设置灰度标识
        setGrayFlagFromPortal(request, response, cuccAuthLoginInfo);
    }

    /**
     * 设置灰度标识
     * @param request
     * @param response
     * @param cuccLoginInfo
     */
    protected void setGrayFlag(HttpServletRequest request, HttpServletResponse response, CuccLoginInfoDTO cuccLoginInfo) {
        String userId = Optional.ofNullable(cuccLoginInfo.getUserInfo())
            .flatMap(u -> Optional.ofNullable(u.getUserId())).map(Object::toString).orElse(null);
        String orgId = Optional.ofNullable(cuccLoginInfo.getOrgInfo()).flatMap(o -> Optional.ofNullable(o.getOrgId()))
            .map(Object::toString).orElse(null);
        String eparchyCode = Optional.ofNullable(cuccLoginInfo.getOrgInfo()).map(CuccOrgInfoDTO::getEparchyCode)
            .orElse(null);
        String provinceCode = Optional.ofNullable(cuccLoginInfo.getOrgInfo()).map(CuccOrgInfoDTO::getProvinceCode)
            .orElse(null);

        boolean isGray = isGray(userId, orgId, eparchyCode, provinceCode);
        handleToken(request, response, isGray, userId, provinceCode);
    }

    /**
     * 设置灰度标识
     * @param request
     * @param response
     * @param cuccLoginInfo
     */
    protected void setGrayFlagFromPortal(HttpServletRequest request, HttpServletResponse response, CuccAuthLoginInfoDTO cuccLoginInfo) {
        String userCode = cuccLoginInfo.getUserInfo().getAccount();
        String userId = Optional.ofNullable(cuccLoginInfo.getUserInfo())
            .flatMap(u -> Optional.ofNullable(u.getOaUserId())).map(Object::toString).orElse(null);
        String orgId = Optional.ofNullable(cuccLoginInfo.getUserInfo()).flatMap(o -> Optional.ofNullable(o.getOrgId()))
            .map(Object::toString).orElse(null);
        String eparchyCode = Optional.ofNullable(cuccLoginInfo.getOrgInfo()).map(CuccAuthOrgInfoDTO::getEparchyCode)
            .orElse(null);
        String provinceCode = Optional.ofNullable(cuccLoginInfo.getOrgInfo()).map(CuccAuthOrgInfoDTO::getProvinceCode)
            .orElse(null);

        boolean isGray = isGray(userId, orgId, eparchyCode, provinceCode);
        handleToken(request, response, isGray, userId, provinceCode);

        if (isGray) {
            saveGrayFlag();
        }
    }

    protected void saveGrayFlag() {
        LoginInfo loginInfo = ContextUtil.getLoginInfo();
        Map<String, Object> extParams = loginInfo.getUserInfo().getExtParams();
        if (KiteMapUtils.isEmpty(extParams)) {
            extParams = new HashMap<String, Object>();
        }

        UserInfo userInfo = ContextUtil.getLoginInfo().getUserInfo();
        extParams.put("grayFlag", "gray");
        userInfo.setExtParams(extParams);

        ContextUtil.getLoginInfo().setUserInfo(userInfo);

        ContextUtil.setLoginInfo(loginInfo);
    }

    /**
     * 判断用户id、组织id、地市编码、省份编码是否需要路由到灰度环境
     * @param userId
     * @param orgId
     * @param eparchyCode
     * @param provinceCode
     * @return
     */
    protected boolean isGray(String userId, String orgId, String eparchyCode, String provinceCode) {
        List<DcPublic> dcPublics = DcPublicCache.getByPkey(LocalDcPublicConsts.DC_2020092501,
            LocalDcPublicConsts.PKEY_GRAY_RULE);
        if (KiteListUtils.isEmpty(dcPublics)) {
            return false;
        }

        userId = appendFirstAndLast(userId, SymbolConsts.COMMA);
        orgId = appendFirstAndLast(orgId, SymbolConsts.COMMA);
        eparchyCode = appendFirstAndLast(eparchyCode, SymbolConsts.COMMA);
        provinceCode = appendFirstAndLast(provinceCode, SymbolConsts.COMMA);
        for (DcPublic dcPublic : dcPublics) {
            String codea = dcPublic.getCodea();
            if (StringUtils.isBlank(codea)) {
                continue;
            }

            codea = appendFirstAndLast(codea, SymbolConsts.COMMA);
            if (StringUtils.equalsAnyIgnoreCase(NAME_USER_ID, dcPublic.getPcode())
                && StringUtils.indexOf(codea, userId) > -1) {
                return true;
            }
            else if (StringUtils.equalsAnyIgnoreCase(NAME_ORG_ID, dcPublic.getPcode())
                && StringUtils.indexOf(codea, orgId) > -1) {
                return true;
            }
            else if (StringUtils.equalsAnyIgnoreCase(NAME_EPARCHY_CODE, dcPublic.getPcode())
                && StringUtils.indexOf(codea, eparchyCode) > -1) {
                return true;
            }
            else if (StringUtils.equalsAnyIgnoreCase(NAME_PROVINCE_CODE, dcPublic.getPcode())
                && StringUtils.indexOf(codea, provinceCode) > -1) {
                return true;
            }
        }

        return false;
    }

    /**
     * 在token的第3位设置灰度标识
     * @param request
     * @param response
     * @param isGray
     */
    protected void handleToken(HttpServletRequest request, HttpServletResponse response, boolean isGray, String userCode, String provinceCode) {
        String token = getCookie(request, NAME_KITE_TOKEN);
        if (StringUtils.isBlank(token) || token.length() < GRAY_INDEX + 1) {
            token = createToken(isGray);
            setCookie(response, NAME_KITE_TOKEN, token);
        }
        else {
            String grayFlag = String.valueOf(token.charAt(GRAY_INDEX));
            if ((isGray && !GRAY_FLAGS.contains(grayFlag)) || (!isGray && GRAY_FLAGS.contains(grayFlag))) {
                grayFlag = getGrayFlag(isGray);
                StringBuilder sb = new StringBuilder(token);
                sb.replace(GRAY_INDEX, GRAY_INDEX + 1, grayFlag);
                setCookie(response, NAME_KITE_TOKEN, sb.toString());
            }
        }

        setCookie(response, "user_code", userCode);
        setCookie(response, "province_code", provinceCode);
    }

    /**
     * 在字符串的首尾添加字符
     * @param str
     * @param fix
     * @return
     */
    protected String appendFirstAndLast(String str, String fix) {
        if (StringUtils.isNoneBlank(str, fix)) {
            return new StringBuilder().append(fix).append(str).append(fix).toString();
        }

        return str;
    }

    /**
     * 生成token，并根据是否路由到灰度环境在第3位设置灰度标识
     * @param isGray
     * @return
     */
    protected String createToken(boolean isGray) {
        String grayFlag = getGrayFlag(isGray);
        String token = Base64.encodeBase64String(UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));
        StringBuffer sb = new StringBuffer(token);
        sb.insert(GRAY_INDEX, grayFlag);
        return sb.toString();
    }

    /**
     * 获取灰度标识
     * @param isGray
     * @return
     */
    protected String getGrayFlag(boolean isGray) {
        if (isGray) {
            int index = ThreadLocalRandom.current().nextInt(4);
            return GRAY_FLAGS.get(index);
        }
        else {
            int index = ThreadLocalRandom.current().nextInt(22);
            return NON_GRAY_FLAGS.get(index);
        }
    }

    /**
     * 对比cookie的值，不一致则重新设置
     * @param request
     * @param response
     * @param name
     * @param value
     */
    @SuppressWarnings("unused")
    protected void compareAndSet(HttpServletRequest request, HttpServletResponse response, String name, String value) {
        if (Objects.isNull(request) || Objects.isNull(response) || StringUtils.isBlank(name)) {
            return;
        }

        String oldValue = getCookie(request, name);
        if (!StringUtils.equals(value, oldValue)) {
            setCookie(response, name, value);
        }
    }

    /**
     * 获取cookie的值
     * @param request
     * @param name
     * @return
     */
    protected String getCookie(HttpServletRequest request, String name) {
        if (Objects.isNull(request) || ObjectUtils.isEmpty(request.getCookies()) || StringUtils.isBlank(name)) {
            return null;
        }

        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if (StringUtils.equals(name, cookie.getName())) {
                return cookie.getValue();
            }
        }

        return null;
    }

    /**
     * 设置cookie
     * @param response
     * @param name
     * @param value
     */
    protected void setCookie(HttpServletResponse response, String name, String value) {
        if (Objects.isNull(response) || StringUtils.isBlank(name)) {
            return;
        }

        Cookie cookie = new Cookie(name, value);
        cookie.setMaxAge(-1);
        cookie.setPath(SymbolConsts.SLASH);
        cookie.setHttpOnly(true);
        response.addCookie(cookie);
    }

    protected void doLogin(String sessionId, String appId, HttpServletRequest request, HttpServletResponse response) {
        if (this.hasLogin(sessionId)) {
            String userCode = ContextUtil.getSimpleLoginInfo().getStaffCode();
            String userId = ContextUtil.getUserId().toString();
            String orgId = ContextUtil.getLoginInfo().getOrgInfo().getOrgId().toString();
            String eparchyCode = ContextUtil.getLoginInfo().getUserInfo().getPostLanId();
            String provinceCode = ContextUtil.getLoginInfo().getUserInfo().getPostProvinceNbr();
            boolean isGray = isGray(userId, orgId, eparchyCode, provinceCode);
            handleToken(request, response, isGray, userId, provinceCode);
        }
        else if (KiteStringUtils.isEmpty(appId) || KiteStringUtils.equals(appId, LocalInfConsts.LOGIN_APP_ID)) {
            CuccAuthLoginInfoDTO cuccAuthLoginInfo = null;
            ICustReceptionService custReceptionService = SpringUtil.getBean(ICustReceptionService.class);
            String sessionJson = CuccSessionIdCache.get(sessionId);
            if (!KiteStringUtils.isEmpty(sessionJson)) {
                cuccAuthLoginInfo = JSONObject.parseObject(sessionJson, CuccAuthLoginInfoDTO.class);
            }
            else {
                cuccAuthLoginInfo = custReceptionService.pretendLogin(sessionId);
                CuccSessionIdCache.put("s" + sessionId, ContextUtil.getHttpSessoinId());
                CuccSessionIdCache.put(sessionId, JSONObject.toJSONString(cuccAuthLoginInfo));
            }

            if (cuccAuthLoginInfo == null) {
                BssException.throwOut("41301002", "S-SYS-00027", "登录已超时，请重新登录");
            }
            ICuccLoginService loginService = SpringUtil.getBean(ICuccLoginService.class);
            loginService.saveLoginInfoFromPortal(sessionId, cuccAuthLoginInfo);
            // 设置灰度标识
            setGrayFlagFromPortal(request, response, cuccAuthLoginInfo);
        }
        else {
            CuccLoginInfoDTO cuccLoginInfo = getLoginInfo(sessionId);
            if (cuccLoginInfo == null) {
                BssException.throwOut("41301002", "S-SYS-00027", "登录已超时，请重新登录");
            }

            ICuccLoginService loginService = SpringUtil.getBean(ICuccLoginService.class);

            loginService.saveLoginInfo(sessionId, cuccLoginInfo);
            // 设置灰度标识
            setGrayFlag(request, response, cuccLoginInfo);
        }
    }

    protected boolean hasLogin(String sessionId) {
        // 运营平台退出会清空缓存，这里需要判断缓存
        String sessionJson = CuccSessionIdCache.get(sessionId);
        return (ContextUtil.getLoginInfo() != null && !KiteStringUtils.isEmpty(sessionJson) &&
            KiteStringUtils.equals(sessionId, (String) ContextUtil.getLoginInfo().getUserInfo().getExtParams().get("session_id")));
    }

    protected abstract boolean handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException, ServletException ;

}
